package mobilesmil.view;

import java.util.ArrayList;
import java.util.Iterator;

import mobilesmil.activity.PlayerActivity;
import mobilesmil.data.*;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * Class extends {@link SurfaceView} to provide complete control for displying  a {@link SmilDataSet}.
 */
public class SmilView extends SurfaceView implements SurfaceHolder.Callback{
	/**
	 * Class provides time control functionality to properly play {@link SmilResource}s from a
	 * {@link SmilDataSet}.
	 */
	private class SmilThread extends Thread{
		private int mCurTime = -1;
		
		private SurfaceHolder mHolder;
		private ArrayList<SmilResource> mPlayingResources = new ArrayList<SmilResource>();
		private int mBeginIndex, mEndIndex;
		private int mBeginMax, mEndMax;
		private SmilDataSet mSmilData;
		
		private Paint mTimePaint;
		private Paint mBackgroundPaint;
		
		/**
		 * This class provides timing control to properly play a {@link SmilDataSet}
		 * which includes drawing the {@link SmilResource}s on the canvas provided.
		 * @param holder Used to get the canvas to draw on.
		 */
		private SmilThread(SurfaceHolder holder){
			mHolder = holder;
			
			//Set the paint for the time slider to dark gray, and to paint text left aligned.
			mTimePaint = new Paint();
			mTimePaint.setColor(Color.DKGRAY);
			mTimePaint.setTextAlign(Align.LEFT);
			mBackgroundPaint = new Paint();
			mBackgroundPaint.setStyle(Style.FILL);
		}
		
		/**
		 * Sets the data set to play.
		 * @param set the data set to play.
		 */
		public boolean setDataSet(SmilDataSet set){
			if(set != null){
				mSmilData = set;
				mBeginIndex = 0;
				mEndIndex = 0;
				mBeginMax = mSmilData.getResourcesByBeginTime().size();
				mEndMax = mSmilData.getResourcesByEndTime().size();
				mBackgroundPaint.setColor(Color.parseColor(set.getBackgroundColorHexString()));
				return true;
			}
			
			return false;
		}
		
		@Override public void run() {
			try{
				mCurTime = -2;
				mState = PLAYING;
				Log.d("SmilView", "STATE: playing");
	            while (getPlayState() > STOPPED) {
	                Canvas c = null;
	                try {
	                    c = mHolder.lockCanvas(null);
	                    synchronized (mHolder) {
	                    	SmilResourceLoadThread loadThread = new SmilResourceLoadThread(c);
	                    	loadThread.start();	//load resources while sleeping
	                    	Thread.sleep(1000);	//pause for a second to keep track of time
	                    	draw(c);			//draw any resource on the canvas
	                    }
	                } catch (InterruptedException e) {
						e.printStackTrace();
					} finally {
	                    // do this in a finally so that if an exception is thrown
	                    // during the above, we don't leave the Surface in an
	                    // inconsistent state
	                    if (c != null) {
	                        mHolder.unlockCanvasAndPost(c);
	                    }
					}
	            }
			}catch(Exception e){
				e.printStackTrace();
				Log.e(this.toString(), e.toString());
			}
		}
		
		private void forwardPlay(Canvas c) throws InterruptedException{
			try{
				if(getPlayState() == PLAYING)
	        		increment();
	        	//add Resources to play
	        	while(mBeginIndex < mBeginMax && mSmilData.getResourcesByBeginTime().get(mBeginIndex).getBeginTime() <= getRunTime()){
	        		if(mSmilData.getResourcesByBeginTime().get(mBeginIndex).getType() == SmilResource.TYPE_AUDIO){
						SmilAudioThread sat = new SmilAudioThread((SmilAudioResource) mSmilData.getResourcesByBeginTime().get(mBeginIndex));
						sat.setPriority(3);
						sat.start();
					}
					else if(mSmilData.getResourcesByBeginTime().get(mBeginIndex).getType() != SmilResource.TYPE_VIDEO)
						mPlayingResources.add(mSmilData.getResourcesByBeginTime().get(mBeginIndex));
					else{
						SmilVideoThread vt = new SmilVideoThread((SmilVideoResource) mSmilData.getResourcesByBeginTime().get(mBeginIndex));
						vt.start();
					}
	        		
	        		mBeginIndex ++;
	        	}
	        	
	        	//remove Resources
				while(mEndIndex < mEndMax && mSmilData.getResourcesByEndTime().get(mEndIndex).getEndTime() <= getRunTime()){
					if(mSmilData.getResourcesByEndTime().get(mEndIndex).getType().equals(SmilResource.TYPE_VIDEO)){
						mSmilData.getResourcesByEndTime().get(mEndIndex).stop(null);
						if(mOwner != null)
							mOwner.displaySurface(((SmilVideoResource)mSmilData.getResourcesByEndTime().get(mEndIndex)).getVideoView(), null);
					}else{
						mSmilData.getResourcesByEndTime().get(mEndIndex).stop(c);
						mPlayingResources.remove(mSmilData.getResourcesByEndTime().get(mEndIndex));
					}
					
					mEndIndex ++;
				}
				
				if(getRunTime() >= mSmilData.getLength())
					stopPlayer();
			}catch(Exception e){
				e.printStackTrace();
				Log.e("Forward Play", e.toString());
			}
		}
		
		/**
		 * Play all the {@link SmilResources} that should be displayed or heard. 
		 * @param canvas
		 */
		public void draw(Canvas canvas){
			canvas.drawPaint(mBackgroundPaint);
			
			Iterator<SmilResource> iter = mPlayingResources.iterator();
			while(iter.hasNext())
				iter.next().play(canvas);
		}
		
		/**
		 * Increments the playback time by one second.
		 */
		private synchronized void increment(){
			mCurTime ++;
		}
		
		/**
		 * @return the time in seconds playback has happened.	
		 */
		public int getRunTime(){
			return mCurTime;
		}
		
		/**
		 * Private thread used to control audio playback. Once instantiated and started 
		 * the thread will automatically take care of playback control.
		 */
		private class SmilAudioThread extends Thread{
			private SmilAudioResource mAudio;
			private int percievedState;
			
			public SmilAudioThread(SmilAudioResource sar){
				mAudio = sar;
			}
			
			@Override public void run() {
				try{
					while ((percievedState = getPlayState()) > STOPPED && getRunTime() < mAudio.getEndTime()){
						if(percievedState == PLAYING)
							mAudio.play(null);
						else
							mAudio.pause();
					}
					mAudio.stop(null);
				}catch(Exception e){
					e.printStackTrace();
					Log.e(mAudio.toString(), e.toString());
				}
			}
		}
		
		/**
		 * Private thread used to load resources during second pauses.
		 */
		private class SmilResourceLoadThread extends Thread{
			private Canvas c;
			
			public SmilResourceLoadThread(Canvas c){
				this.c = c;
			}
			
			@Override public void run(){
				try {
					forwardPlay(c);
				} catch (InterruptedException e) {
					e.printStackTrace();
					Log.e(this.toString(), e.toString());
				}
			}
		}
		
		/**
		 * Private thread used to control video playback. Once instantiated and started
		 * the thread will automatically take care of playback control.
		 */
		private class SmilVideoThread extends Thread{
			private SmilVideoResource mVideo;
			
			public SmilVideoThread(SmilVideoResource video){
				mVideo = video;
				if(mOwner != null){
					mOwner.displaySurface(mVideo.getVideoView(), mVideo.getLayouParams());
					Log.i("VideoThread", "created " + mVideo.getSource());
				}
			}
			
			@Override public void run(){
				try{
					int percivedState;
					
					while((percivedState = getPlayState()) > STOPPED && getRunTime() < mVideo.getEndTime()){
						try {
							if(percivedState == PLAYING){
								mVideo.play(null);
								Thread.sleep(230);
								//mVideo.pause();
							}else
								mVideo.pause();
						} catch (InterruptedException e){
							Log.e("VideoThread", e.getMessage());
						}
					}
					Log.i("Video", mVideo.getSource() + " has stoped");
					mVideo.stop(null);
				}catch(Exception e){
					e.printStackTrace();
					Log.e(mVideo.getSource(), e.toString());
				}
			}
		}
	}
	/**
	 * 1
	 */
	final public static int PLAYING = 1;
	/**
	 * 0
	 */
	final public static int PAUSED = 0;
	/**
	 * -1
	 */
	final public static int STOPPED = -1;
	/**
	 * -2
	 */
	final public static int PLAYED = -2;
	
	private int mState = STOPPED;
	private SmilThread mTime;
	private SmilDataSet mPlaySet;
	private boolean mControlVisible = true;
	private boolean mSurfaceReady = false;
	private boolean mWantToPlay = false;
	private PlayerActivity mOwner;
		
 	public SmilView(Context context, AttributeSet attrs){
		super(context, attrs);
		
		SurfaceHolder holder = getHolder();
		holder.addCallback(this);
		
		mTime = new SmilThread(holder);
		
		this.setFocusable(true);
	}

	public void setCallingActivity(PlayerActivity owner){
		mOwner = owner;
	}
	
	/**
	 * Used to start playback on the given {@link SmilDataSet}.
	 * @param data The data to play.
	 */
	public synchronized void playPlayer(SmilDataSet data){
		
		if(mState == STOPPED && mSurfaceReady){
			if(mTime.setDataSet(data)){
				mTime.start();
			}
		}else{
			synchronized(mPlaySet){
				Log.d("Player", "wanting to play");
				mWantToPlay = true;
				mPlaySet = data;
			}
		}
	}
	
	/**
	 * Used to restart playback immediately succeeding a call to {@link #pausePlayer()}
	 */
	public synchronized void resumePlayer(){
		if(mState == PAUSED)
			mState = PLAYING;
	}
	
	/**
	 * Used to pause playback. After this is called playback must be resumed by calling
	 * {@link #resumePlayer()}.
	 */
	public synchronized void pausePlayer(){
		if(mState == PLAYING)
			mState = PAUSED;
	}
	
	/**
	 * Used to stop playback of the {@link SmilDataSet}. After this is called playback
	 * will only start again by calling {@link #play(SmilDataSet)}
	 */
	public synchronized void stopPlayer(){
		if(mState >= PAUSED)
			mState = PLAYED;
	}
	
	/**
	 * Used to get state of {@link SmilDataSet} playback.
	 * @return the state of playback.
	 */
	public int getPlayState(){
		return mState;
	} 
	
	/**
	 * @return the visibility of the playback controlls
	 */
	public boolean getControlVisiblity(){
		return mControlVisible;
	}
	
	/**
	 * Sets if playback controlls are visible or not.
	 * @param vis true controlls are visible. false controlls are not visible
	 */
	public void setControlVisible(boolean vis){
		mControlVisible = vis;
	}
	
	/**
	 * @return the number of seconds the SMIL has been playing
	 */
	public int getRuntime(){
		return mTime.getRunTime();
	}

	@Override public void surfaceCreated(SurfaceHolder arg0) {
		Log.d("Player", "surface created");
		this.setFocusable(true);
		mSurfaceReady = true;
		if(mWantToPlay)
			playPlayer(mPlaySet);
	}

	@Override public void surfaceDestroyed(SurfaceHolder arg0) {
		 boolean retry = true;
		 mState = STOPPED;
	        while (retry) {
	            try {
	            	mTime.join();
	                retry = false;
	            } catch (InterruptedException e) {}
	        }
	}

	@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
}